parser grammar Javasint;

/*
//Solucion del if con backtrack=false:
if_statement: IF^ ifexp ifbloque (elseif)?;
ifexp: LPARENTHESIS! expression RPARENTHESIS!; 
ifbloque: (bloque_stats | statement); 
elseif: ELSE^ (bloque_stats | statement);

*/


options {
  language = Java;
  tokenVocab = Javalex;
  backtrack = true; //para resolver ambiguedades
  //k = 3;
}

@header {
  package samplejava.antlr;
  
  import samplejava.compiler.*;
}

@members {
	static final int NULO = -99; //para la funcion gen: que no genere el parametro
	//static final CADENA_NULL = "";
	
	SymTable tabla = new SymTable();
	boolean tipoFunc = false; //para saber si funcion devuelve tipo o no
	StringBuilder codigoSAM = new StringBuilder(); // Contiene el codigo SAM que se va generando dinamicamente
	boolean debug = false;
	boolean ref = false;
	boolean esFloat = false;
	
	void printTS(String mensaje) {
		if (debug)
		  System.out.println(mensaje + tabla);
	}
	
	//funciones para generar codigo
	void genComment(String comment) {
		codigoSAM.append(";" + comment); //el \n ya lo hace gen
	}
	
	void genEtiqueta(String name) {
	   codigoSAM.append("\n\n" + name + ":");
	}
	
    void gen(String nameInstr) { //para funciones HALT, CALLI etc.
        codigoSAM.append("\n" + nameInstr);
    }
    
	void gen(String nameInstr, int n, int d, String reg) {
		codigoSAM.append("\n" + nameInstr);
		if (n != NULO)
		  codigoSAM.append(" (" + n + ")");
		if (d != NULO)
          codigoSAM.append(" " + d);
        if (!reg.equals(""))
          codigoSAM.append("[" + reg + "]");
	}
	
    void genCall(int n, String name) {
       codigoSAM.append("\nCALL (" + n + ") " + name);
    }
    
    void genCall(String reg, String name) {
       codigoSAM.append("\nCALL (" + reg + ") " + name);
    }
    
    void genOperation(int tipo, String op, int tipo1, int tipo2) { //genera codigo para: E -> E1 op E2
    	if ((tipo1 == Tipos.INT) && (tipo2 == Tipos.INT)) 
    	   genCall(Tipos.getSize(tipo), op);
    	else if ((tipo1 == Tipos.INT) && (tipo2 == Tipos.LONG)) {
    	   genCall(Tipos.getSize(tipo1), "to21");
    	   genCall(Tipos.getSize(tipo), op);
    	}
    	else if ((tipo1 == Tipos.LONG) && (tipo2 == Tipos.INT)) {
    	   genCall(Tipos.getSize(tipo), "to22");
           genCall(Tipos.getSize(tipo), op);
    	}
        else if ((tipo1 == Tipos.LONG) && (tipo2 == Tipos.LONG))
           genCall(Tipos.getSize(tipo), op);
    }
    
    void genAssignL(String id) {
           EntryTS entrada = tabla.find(id);
           if (entrada != null) {
              if (entrada.isGlobal())
                gen("LOAD", entrada.getSize(), entrada.getOffset(), "SB"); 
              else
                gen("STORE", entrada.getSize(), entrada.getOffset(), "LB");
           }
    }
    
    void genAssignR(String id, String op) {
           EntryTS entrada = tabla.find(id);
           if (entrada != null) {
              genCall(entrada.getSize(), op);
              genCall(entrada.getSize(), "id");
              if (entrada.isGlobal())
                  gen("STORE", entrada.getSize(), entrada.getOffset(), "SB");
              else
                  gen("STORE", entrada.getSize(), entrada.getOffset(), "LB");
           }
    }
    
    //fin funciones de generar codigo
    
	
	int crearTipo(int tipo1, int tipo2) { //dada Expresion: E1 op E2, devuelve tipo devuelto
	   if ((tipo1 == Tipos.INT) && (tipo2 == Tipos.INT)) return Tipos.INT;
	   else if ((tipo1 == Tipos.INT) && (tipo2 == Tipos.LONG)) return Tipos.LONG;
	   else if ((tipo1 == Tipos.LONG) && (tipo2 == Tipos.INT)) return Tipos.LONG;
       else if ((tipo1 == Tipos.LONG) && (tipo2 == Tipos.LONG)) return Tipos.LONG;
       else return Tipos.TYPE_ERROR;
	}
	
	
  protected void mismatch(IntStream input, int ttype, BitSet follow)
    throws RecognitionException
  {
    throw new MismatchedTokenException(ttype, input);
  } 
}


@rulecatch { 
  catch (RecognitionException e) {
    throw e;
  } 
}



//**************************************************************************************/
//GRAMATICA:

//program: data_def* function_def* EOF ;

program: def def* EOF { gen("HALT"); printTS("\nFINAL: \n"); } ;
def: (INT? ID LPARENTHESIS) => function_def | data_def;

data_def:
        INT d1=declarator { tabla.lookUp($d1.name, EntryTS.VAR, 0, Tipos.INT); gen("PUSH", NULO, 1, ""); }  
        (COMMA d2=declarator { tabla.lookUp($d2.name, EntryTS.VAR, 0, Tipos.INT); gen("PUSH", NULO, 1, "");  })* SEMICOLON
        
        | LONG d1=declarator { tabla.lookUp($d1.name, EntryTS.VAR, 0, Tipos.LONG); gen("PUSH", NULO, 2, "");  }  
        (COMMA d2=declarator { tabla.lookUp($d2.name, EntryTS.VAR, 0, Tipos.LONG); gen("PUSH", NULO, 2, ""); })* SEMICOLON
        
         | FLOAT d1=declarator { tabla.lookUp($d1.name, EntryTS.VAR, 0, Tipos.FLOAT); gen("PUSH", NULO, 4, "");  }  
        (COMMA d2=declarator { tabla.lookUp($d2.name, EntryTS.VAR, 0, Tipos.FLOAT); gen("PUSH", NULO, 4, ""); })* SEMICOLON;
         
declarator returns [String name]: 
    id=ID {$declarator.name = $id.getText();};
    
function_def: 
        { printTS("\nANTES DE FUNCION:\n"); } 
        (INT {tipoFunc = true;} )? 
        function_header  //funcion_header introduce funcion, blkPush(), parametros     
        function_body 
        { printTS("\nAL FINAL DE LA FUNCION: \n");
          tabla.blkPop(); 
          printTS("\nDESPUES DE POP: \n");
          if (tipoFunc)
             gen("RETURN", 1, 1, ""); //funcion retorna algo
          else 
             gen("RETURN", 0, 1, ""); //no retorna ningun valor
            
          tipoFunc = false; //para las siguientes funciones 
        } ;
        
function_header: //returns [String name] : 
            d=declarator 
           { genEtiqueta($d.name);
            if ($d.name.equals("main"))
               tabla.modify("main", EntryTS.FUNC, 0, Tipos.INT);
            else {
                if (tipoFunc)
                    tabla.lookUp($d.name, EntryTS.FUNC, 0, Tipos.INT); 
                else
                    tabla.lookUp($d.name, EntryTS.FUNC, 0, 0); 
            }
            
             tabla.blkPush(); //push despues de nombre de funcion y antes de parametros
            }
            
            parameter_list;
            
            
parameter_list: LPARENTHESIS identifier_list? RPARENTHESIS parameter_decl*;

identifier_list: (AND_BIN { ref = true;} )? id1=ID 
         { 
           if (ref) {
               tabla.lookUp($id1.getText(), EntryTS.PARAM_REF, 0, Tipos.INT); //tipo int por defecto
               gen("LOADA", NULO, tabla.findOffset($id1.getText()), "LB"); //paso por referencia 
           }
           else {
               tabla.lookUp($id1.getText(), EntryTS.PARAM, 0, Tipos.INT); //tipo int por defecto 
               gen("LOAD", 1, tabla.findOffset($id1.getText()), "LB"); //paso por valor
           }
           
           ref = false; 
          } 
         
         (COMMA (AND_BIN { ref = true;} )? id2=ID 
         { 
           if (ref) {
               tabla.lookUp($id2.getText(), EntryTS.PARAM_REF, 0, Tipos.INT); //tipo int por defecto
               gen("LOADA", NULO, tabla.findOffset($id2.getText()), "LB"); //paso por referencia 
           }
           else {
               tabla.lookUp($id2.getText(), EntryTS.PARAM, 0, Tipos.INT); //tipo int por defecto 
               gen("LOAD", 1, tabla.findOffset($id2.getText()), "LB"); //paso por valor
           }
           
           ref = false; 
          }  )*;
          

parameter_decl: INT d1=declarator 
               { tabla.lookUp($d1.name, EntryTS.PARAM, 0, Tipos.INT); 
                  gen("LOAD", 1, tabla.findOffset($d1.name), "LB"); }
                  
               (COMMA d2=declarator 
               { tabla.lookUp($d2.name, EntryTS.PARAM, 0, Tipos.INT); 
                 gen("LOAD", 1, tabla.findOffset($d2.name), "LB"); })* SEMICOLON;
               
               
function_body: LCURLY data_def* statement* RCURLY;
statement: (expression { gen("POP", Tipos.getSize($expression.tipo), NULO, ""); })? SEMICOLON 
        | LCURLY { tabla.blkPush();} data_def* statement* { tabla.blkPop();} RCURLY
        | IF LPARENTHESIS expression RPARENTHESIS statement (ELSE statement)?
        | WHILE LPARENTHESIS expression RPARENTHESIS statement
        | BREAK SEMICOLON
        | CONTINUE SEMICOLON
        | RETURN (expression)? SEMICOLON 
         { if (tabla.getBlockLevel() == 0)
         	gen("HALT");
           else
            gen("RETURN", 0, 0, ""); //aun por saber
         };

expression returns [int tipo]: binary { $tipo = $binary.tipo; }
            (COMMA { gen("POP", Tipos.getSize($binary.tipo), NULO, ""); } expression)*;
            
            
binary returns [long value, int tipo]: 
       id=ID ASSIGN b=binary 
       { EntryTS entrada = tabla.modifyValue($id.getText(), $b.value, 0);
       	 genCall(entrada.getSize(), "id");
         if (entrada != null) //si es null: error semantico
            if (entrada.getKind() == EntryTS.PARAM_REF) //se podria tambien con tabla.findKind()
               gen("STOREI", entrada.getSize(), NULO, "");
            else 
               if (entrada.isGlobal())
                 gen("STORE", entrada.getSize(), entrada.getOffset(), "SB"); 
               else
                 gen("STORE", entrada.getSize(), entrada.getOffset(), "LB"); 
       }
       
        
       | id=ID { genAssignL($id.getText()); }
         APLUS binary { genAssignR($id.getText(), "add"); }
       
       | id=ID { genAssignL($id.getText()); }
         AMULT binary { genAssignR($id.getText(), "mult"); }
       
       | id=ID { genAssignL($id.getText()); }
         ADIV binary { genAssignR($id.getText(), "div"); }
       
       | id=ID { genAssignL($id.getText()); }
         AMINUS binary { genAssignR($id.getText(), "sub"); }
       
       | id=ID { genAssignL($id.getText()); }
         AMOD binary { genAssignR($id.getText(), "mod"); }
            
            
       //***** E1 op E2  *****
       | unary PLUS binary
             { $value = $u.value - $b.value;
          $tipo = crearTipo($u.tipo, $b.tipo); 
          genOperation($tipo, "add", $u.tipo, $b.tipo);
         }
       
       | u=unary MINUS b=binary 
        { $value = $u.value - $b.value;
          $tipo = crearTipo($u.tipo, $b.tipo); 
          genOperation($tipo, "sub", $u.tipo, $b.tipo);
         }
          
       | unary ASTERISK binary       
       { $value = $u.value - $b.value;
          $tipo = crearTipo($u.tipo, $b.tipo); 
          genOperation($tipo, "mult", $u.tipo, $b.tipo);
         }
         
       | unary DIV binary 
             { $value = $u.value - $b.value;
          $tipo = crearTipo($u.tipo, $b.tipo); 
          genOperation($tipo, "div", $u.tipo, $b.tipo);
         }
         
       | unary MOD binary
             { $value = $u.value - $b.value;
          $tipo = crearTipo($u.tipo, $b.tipo); 
          genOperation($tipo, "mod", $u.tipo, $b.tipo);
         }
         
       | unary AND_BIN binary
       | unary OR_BIN binary
       | unary POW binary
       
       | unary LESS binary
       | unary GREATER binary
       | unary NOTEQUAL binary
       | unary EQUALS binary
       | unary LESSEQUAL binary
       | unary GREATEQUAL binary
       
       | unary { $binary.value = $unary.value; $binary.tipo = $unary.tipo;} ;
       
unary returns [long value, int tipo] : 
            INC id=ID 
            { if (tabla.isGlobal($id.getText()))
               gen("LOADA", NULO, tabla.findOffset($id.getText()), "SB");
              else
               gen("LOADA", NULO, tabla.findOffset($id.getText()), "LB");
               
              genCall(tabla.findSize($id.getText()), "inc");
            }
            
            | ID INC
            
            | DEC ID             
            { // $tipo = tabla.findTipo($id.getText());
              if (tabla.isGlobal($id.getText()))
               gen("LOADA", NULO, tabla.findOffset($id.getText()), "SB");
              else
               gen("LOADA", NULO, tabla.findOffset($id.getText()), "LB");
               
              genCall(tabla.findSize($id.getText()), "dec");
            }
            
            | ID DEC
            | primary { $unary.value = $primary.value; $unary.tipo = $primary.tipo; } ;
            
primary returns [long value, long dvalue, int tipo] : 
       id=ID 
       { $primary.tipo = tabla.findTipo($id.getText()); 
       	 $primary.value = tabla.findValue($id.getText()); 
       	 if (tabla.isGlobal($id.getText()))
       	    gen("LOAD", tabla.findSize($id.getText()), tabla.findOffset($id.getText()), "SB");
       	 else
       	    gen("LOAD", tabla.findSize($id.getText()), tabla.findOffset($id.getText()), "LB");
       }
       
        | n=NUMERO  // NUMERO = CONST
           
         (DOT n2=NUMERO { esFloat = true;} )? 
         
          { $primary.tipo = Tipos.INT; //por defecto un numero es int
          $primary.value = Long.parseLong($n.getText()); 
          gen("LOADL", NULO,  new Long($primary.value).intValue(), ""); 
          if (esFloat) {
            $dvalue = Long.parseLong($n2.getText());
            esFloat = false;
          }
            
         }
          
        | LPARENTHESIS expression RPARENTHESIS
        | id=ID LPARENTHESIS argument_list? RPARENTHESIS 
          { genCall("SB", $id.getText()); };  //function_statement
        
argument_list: binary (COMMA binary)*; 
       
